import codecs
from math import isinf, isnan

import six

from . import grammar


def NormaliseCharsetName(s:str):
    if not isinstance(s, six.text_type):
        s = s.decode('utf-8')
    return (codecs.lookup(s).name.replace('_', '-').upper().replace('ISO8859', 'ISO-8859'))

def InterpretGoPoint(s:str, size:int):
    if s == '' or (s == 'tt' and size <= 19):
        return None 
    row_s, col_s= s 
    col = ord(col_s) - 97
    row = ord(row_s) - 97
    if not ((0 <= col < size ) and (0 <= row < size)):
        raise ValueError
    return row, col 

def SerialiseGoPoint(move, size):
    if not 1 <= size <= 26:
        raise ValueError
    if move is None:
        if size <= 19:
            return 'tt'
        else:
            return ''
    row,col = move 
    if not ((0<=col<size) and (0<= row < size)):
        raise ValueError
    col_s = "abcdefghijklmnopqrstuvwxy"[col]
    row_s = "abcdefghijklmnopqrstuvwxy"[row]
    return row_s + col_s  

class _Context:
    def __init__(self, size, encoding) -> None:
        self.size = size 
        self.encoding = encoding

def InterpretNone(s:str, context=None):
    return True 

def SerialiseNone(s, context=None):
    return ''

def InterpretNumber(s:str, context=None):
    return int(s,10)

def SerialiseNumber(i, context=None):
    return f'{i}'

def InterpretReal(s:str, context=None):
    result = float(s)
    if isinf(result):
        raise ValueError('infinite')
    if isnan(result):
        raise ValueError('not a number')
    return result

def SerialiseReal(f, context=None):
    f = float(f)
    try:
        i = int(f)
    except OverflowError:
        raise ValueError
    if f == i:
        return str(i)
    s = repr(f)
    if 'e-' in s:
        return '0'
    return s 

def InterpretDouble(s:str, context=None):
    if s.strip() == '2':
        return 2 
    else:
        return 1
    
def SerialiseDouble(i, context=None):
    if i == 2:
        return '2'
    return '1'

def InterpretColour(s:str, context=None):
    colour = s.lower()
    if colour not in ('b', 'w'):
        raise ValueError
    return colour

def SerialiseColour(colour, context=None):
    if colour not in ('b', 'w'):
        raise ValueError
    return colour.upper()

def TransCode(s: str, encoding):
    u = s.decode(encoding)
    if encoding == 'UTF-8':
        return s 
    else:
        return u.encode('utf-8')
    
def InterpretSimpleText(s:str, context):
    return TransCode(grammar.SimpleTextValue(s), context.encoding)

def SerialiseSimpleText(s, context):
    if context.encoding != 'UTF-8':
        s = s.decode('utf-8').encode(context.encoding)
    return grammar.EscapeText(s)

def InterpretText(s:str, context):
    return TransCode(grammar.TextValue(s), context.encoding)

def SerialiseText(s, context): 
    if context.encoding != 'UTF-8':
        s = s.decode('utf-8').encode(context.encoding)
    return grammar.EscapeText(s)

def InterpretPoint(s:str, context:_Context):
    result = InterpretGoPoint(s, context.size)
    if result is None:
        raise ValueError
    return result

def SerialisePoint(point, context:_Context):
    if point is None:
        raise ValueError
    return SerialiseGoPoint(point, context.size)

def InterpretMove(s:str, context:_Context):
    return InterpretGoPoint(s, context.size)

def SerialiseMove(move, context):
    return SerialiseGoPoint(move, context.size)

def InterpretPointList(values:'list[str]', context:_Context):
    result = set()
    for s in values:
        p1, is_rectangle, p2 = s.partition(':')
        if is_rectangle:
            top, left = InterpretPoint(p1, context)
            bottom, right = InterpretPoint(p2, context)
            if not (bottom <= top and left <= right):
                raise ValueError
            for row in range(bottom, top+1):
                for col in range(left, right+1):
                    result.add((row,col))
        else:
            pt = InterpretPoint(p1, context)
            result.add(pt)
    return result 

def SerialisePointList(points, context:_Context):
    result = [SerialisePoint(p,context) for p in points]
    result.sort()
    return result

def InterpretAP(s:str, context:_Context):
    application, version = grammar.ParseCompose(s)
    if version is None:
        version = ''
    return (InterpretSimpleText(application, context), 
            InterpretSimpleText(version, context))

def SerialiseAP(value, context:_Context):
    app, version = value 
    return grammar.Compose(SerialiseSimpleText(app, context),
                           SerialiseSimpleText(version, context))

def InterpretARLN_List(values, context:_Context):
    result = []
    for s in values:
        p1,p2 = grammar.ParseCompose(s)
        result.append((InterpretPoint(p1,context), 
                       InterpretPoint(p2,context)))
    return result

def SerialiseARLN_List(values, context:_Context):
    return [':'.join(
        (SerialisePoint(p1,context), SerialisePoint(p2,context))) 
        for p1,p2 in values]

def InterpretFG(s:str, context:_Context):
    if s == '':
        return None 
    flags, name = grammar.ParseCompose(s)
    return int(flags), InterpretSimpleText(name, context)

def SerialiseFG(value, context):
    if value is None:
        return ''
    flags, name = value 
    return str(flags) + ':' + SerialiseSimpleText(name, context)

def InterpretLB_List(values, context):
    result = []
    for s in values:
        point, label = grammar.ParseCompose(s)
        result.append((InterpretPoint(point, context),
                       InterpretSimpleText(label, context)))
        
    return result 

def SerialiseLB_List(values,context):
    return [':'.join(
        (SerialisePoint(pt,context), SerialiseSimpleText(text, context))) 
        for pt,text in values]

class PropertyType:
    def __init__(self, interpreter, serialiser, uses_list, allows_empty_list=False) -> None:
        self.interpreter = interpreter
        self.serialiser = serialiser
        self.uses_list = bool(uses_list)
        self.allows_empty_list = bool(allows_empty_list)

def MakePropertyType(type_name, allows_empty_list=False):
    return PropertyType(
        globals()['Interpret'+ type_name ],
        globals()['Serialise' + type_name],
        uses_list= (type_name.endswith('List')),
        allows_empty_list=allows_empty_list)

PropertyTypesByName = {
    'none': MakePropertyType('None'),
    'number': MakePropertyType('Number'),
    'real': MakePropertyType('Real'),
    'double': MakePropertyType('Double'),
    'colour': MakePropertyType('Colour'),
    'simpletext': MakePropertyType('SimpleText'),
    'text': MakePropertyType('Text'),
    'point': MakePropertyType('Point'),
    'move': MakePropertyType('Move'),
    'point_list': MakePropertyType('PointList'),
    'point_elist': MakePropertyType('PointList', allows_empty_list=True),
    'stone_list': MakePropertyType('PointList'),
    'AP': MakePropertyType('AP'),
    'ARLN_list': MakePropertyType('ARLN_List'),
    'FG': MakePropertyType('FG'),
    'LB_list': MakePropertyType('LB_List'),
}

P = PropertyTypesByName
PropertyTypesByIdent = {
    'AB': P['stone_list'],
    'AE': P['point_list'],
    'AN': P['simpletext'],
    'AP': P['AP'],
    'AR': P['ARLN_list'],
    'AW': P['stone_list'],
    'B': P['move'],
    'BL': P['real'],
    'BM': P['double'],
    'BR': P['simpletext'],
    'BT': P['simpletext'],
    'C': P['text'],
    'CA': P['simpletext'],
    'CP': P['simpletext'],
    'CR': P['point_list'],
    'DD': P['point_elist'],
    'DM': P['double'],
    'DO': P['none'],
    'DT': P['simpletext'],
    'EV': P['simpletext'],
    'FF': P['number'],
    'FG': P['FG'],
    'GB': P['double'],
    'GC': P['text'],
    'GM': P['number'],
    'GN': P['simpletext'],
    'GW': P['double'],
    'HA': P['number'],
    'HO': P['double'],
    'IT': P['none'],
    'KM': P['real'],
    'KO': P['none'],
    'LB': P['LB_list'],
    'LN': P['ARLN_list'],
    'MA': P['point_list'],
    'MN': P['number'],
    'N': P['simpletext'],
    'OB':P['number'],
    'ON': P['simpletext'],
    'OT':P['simpletext'],
    'OW': P['number'],
    'PB': P['simpletext'],
    'PC': P['simpletext'],
    'PL': P['colour'],
    'PM': P['number'],
    'PW': P['simpletext'],
    'RE': P['simpletext'],
    'RO': P['simpletext'],
    'RU': P['simpletext'],
    'SL':P['point_list'],
    'SO': P['simpletext'],
    'SQ': P['point_list'],
    'ST': P['number'],
    'SZ': P['number'],
    'TB': P['point_elist'],
    'TE': P['double'],
    'TM': P['real'],
    'TR': P['point_list'],
    'TW': P['point_elist'],
    'UC': P['double'],
    'US': P['simpletext'],
    'V': P['real'],
    'VW': P['point_elist'],
    'W': P['move'],
    'WL':P['real'],
    'WR':P['simpletext'],
    'WT':P['simpletext'],
}
TextPropertyType = P['text']

del P 

class Presenter(_Context):
    def __init__(self, size, encoding) -> None:
        try:
            encoding = NormaliseCharsetName(encoding)
        except LookupError:
            raise ValueError(f'Unknown encoding {encoding}')
        super().__init__(size,encoding)
        self.property_types_by_ident = PropertyTypesByIdent.copy()
        self.default_property_type = TextPropertyType

    def GetPropertyType(self, id):
        return self.property_types_by_ident[id]
    
    def RegisterProperty(self, id, property_type):
        self.property_types_by_ident[id] = property_type

    def DeregisterProperty(self, id):
        del self.property_types_by_ident[id]

    def SetPrivatePropertyType(self, property_type):
        self.default_property_type = property_type

    def GetEffectivePropertyType(self, id):
        try:
            return self.property_types_by_ident[id]
        except KeyError:
            result = self.default_property_type
            if result is None:
                raise ValueError('Unkonown property')
            return result
        
    def InterpretAsType(self, property_type:PropertyType, raw_values):
        if not raw_values:
            raise ValueError('No raw values')
        if property_type.uses_list:
            if raw_values == ['']:
                raw = []
            else:
                raw = raw_values
        else:
            if len(raw_values) > 1:
                raise ValueError('Multiple values')
            raw = raw_values[0]
        return property_type.interpreter(raw, self)
    
    def Interpret(self, id, raw_values):
        return self.InterpretAsType(self.GetEffectivePropertyType(id), raw_values=raw_values)
    
    def SerialiseAsType(self, property_type:PropertyType, value):
        serialised = property_type.serialiser(value, self)
        if property_type.uses_list:
            if serialised == []:
                if property_type.allows_empty_list:
                    return ['']
                else:
                    raise ValueError('Empty list')
            return serialised
        else:
            return [serialised]
        
    def Serialise(self, id, value):
        return self.SerialiseAsType(self.GetEffectivePropertyType(id), value)

        
